package munci.old.use;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject.Kind;


public class DynamicCompiler {

	static class MemoryFile extends SimpleJavaFileObject {

		MemoryFile(String name, Kind kind) {
			super(toURI(name), kind);
		}

		ByteArrayOutputStream bytesOut;

		@Override public InputStream openInputStream() {
			return new ByteArrayInputStream(bytesOut.toByteArray());
		}

		@Override public OutputStream openOutputStream() {
			return bytesOut = new ByteArrayOutputStream();
		}
	}

	private final JavaCompiler compiler;
	private final Map<String, MemoryFile> fileMap;
	private final DiagnosticCollector<JavaFileObject> diagnostics;
	private final JavaFileManager manager;
	private final ClassLoader loader;
	
	public DynamicCompiler() {
		
		this.compiler = ToolProvider.getSystemJavaCompiler();
		this.fileMap = new HashMap<String, MemoryFile>();
		this.diagnostics = new DiagnosticCollector<JavaFileObject>();
		
		this.manager = new ForwardingJavaFileManager<StandardJavaFileManager>(
					compiler.getStandardFileManager(diagnostics, null, null)) {
						@Override public JavaFileObject getJavaFileForOutput(
								Location location, String name, Kind kind, FileObject sibling) {
							MemoryFile file = new MemoryFile(name, kind);
							fileMap.put(name, file);
							return file;
						}
					};
					
		this.loader = new ClassLoader() {
			@Override protected Class<?> findClass(String name) throws ClassNotFoundException {
				MemoryFile file = fileMap.get(name);
				if (file != null) {
					byte[] bytes = file.bytesOut.toByteArray();
					return defineClass(name, bytes, 0, bytes.length);
				}
				return super.findClass(name);
			}
		};
	}
	
	public Class<?> compile(final CharSequence source, String name) throws Exception {
		
		JavaFileObject sourceFile = new SimpleJavaFileObject(
				toURI(name.replace('.', '/') + ".java"), Kind.SOURCE) {
			@Override public CharSequence getCharContent(boolean ignoreEncodingErrors) {
				return source;
			}
		};

		CompilationTask task = compiler.getTask(
				null, manager, diagnostics,
				null,
//				Arrays.asList("-cp", System.getProperty("java.class.path")),
				null, Arrays.asList(sourceFile));

		if (!task.call()) {
			for (Diagnostic<?> dm : diagnostics.getDiagnostics())
				System.err.println(dm);
			
			throw new RuntimeException("Compilation failed");
		}

//		System.out.println("generated classes: " + fileMap.keySet());
		return Class.forName(name, false, loader);
	}
	
	public Class<?> getClass(String name) throws ClassNotFoundException {
		return Class.forName(name, false, loader);
	}
	
	private static URI toURI(String name) {
		try {
			return new URI(name);
		} catch (URISyntaxException e) {
			throw new RuntimeException(e);
		}
	}

	public static void main(String[] args) throws Exception {
		String source = 
					"\n    package just.generated;"
				+	"\n    public class Hello {"
				+	"\n	       public static void main(String[] args) {"
				+	"\n            java.lang.System.out.println(new Object() {"
				+ 	"\n                public String toString() {"
				+	"\n                    return \"just hello!\";"
				+ 	"\n                }" 
				+	"\n            });" 
				+	"\n        }"
				+ 	"\n    }"
				;

		String className = "just.generated.Hello";

		new DynamicCompiler().compile(source, className)
				.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
	}
}